﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Epsilon.Expr;
using Epsilon.Typing;

namespace Epsilon.Output
{
    public partial class Writer
    {
        public void Write(Expression e)
        {
            if (e is IntExpr)
                Write((IntExpr)e);
            else if (e is BoolExpr)
                Write((BoolExpr)e);
            else if (e is VarExpr)
                Write((VarExpr)e);
            else if (e is UnitExpr)
                Write((UnitExpr)e);
            else if (e is TxExpr)
                Write((TxExpr)e);
            else if (e is TupleExpr)
                Write((TupleExpr)e);
            else if (e is StringExpr)
                Write((StringExpr)e);
            else if (e is SelfExpr)
                Write((SelfExpr)e);
            else if (e is ScOpExpr)
                Write((ScOpExpr)e);
            else if (e is ListOpExpr)
                Write((ListOpExpr)e);
            else if (e is PreOpExpr)
                Write((PreOpExpr)e);
            else if (e is BinOpExpr)
                Write((BinOpExpr)e);
            else if (e is ProjExpr)
                Write((ProjExpr)e);
            else if (e is PidExpr)
                Write((PidExpr)e);
            else if (e is ParenExpr)
                Write((ParenExpr)e);
            else if (e is ListExpr)
                Write((ListExpr)e);
            else if (e is ListTailExpr)
                Write((ListTailExpr)e);
            else if (e is NilExpr)
                Write((NilExpr)e);
            else if (e is LabExpr)
                Write((LabExpr)e);
            else if (e is RecordExpr)
                Write((RecordExpr)e);
            else if (e is RecModExpr)
                Write((RecModExpr)e);
            else if (e is RecProjExpr)
                Write((RecProjExpr)e);
            else if (e is PatExpr)
                Write((PatExpr)e);
            else if (e is SeqExpr)
                Write((SeqExpr)e);
            else if (e is IfExpr)
                Write((IfExpr)e);
            else if (e is AtomExpr)
                Write((AtomExpr)e);
            else if (e is FnRefExpr)
                Write((FnRefExpr)e);
            else if (e is ListCaseExpr)
                Write((ListCaseExpr)e);
            else if (e is ApplyExpr)
                Write((ApplyExpr)e);
            else if (e is SpawnExpr)
                Write((SpawnExpr)e);
            else if (e is RxExpr)
                Write((RxExpr)e);
            else if (e is FunExpr)
                Write((FunExpr)e);
            else if (e is FunctionExpr)
                Write((FunctionExpr)e);
            else
                throw new OutputException("Invalid expression");     
        }

        public void Write(IntExpr e)
        {
            Output(e.Value);
        }

        public void Write(BoolExpr e)
        {
            Output(e.Value);
        }

        public void Write(VarExpr e)
        {
            Output(e.Var);
        }

        public void Write(UnitExpr e)
        {
            Output("void");
        }

        public void Write(TxExpr e)
        {
            Write(e.Pid);
            Output(" ! ");
            Write(e.Val);
        }

        public void Write(TupleExpr e)
        {
            Output("{");
            for (int i = 0; i < e.Expressions.Count; i++)
            {
                if (i > 0)
                    Output(", ");
                Write(e.Expressions[i]);
            }
            Output("}");
        }

        public void Write(StringExpr e)
        {
            if (e.Strings.Count == 0)
                return;

            Output("\"" + e.Strings[0] + "\"");

            IncreaseIndent();
            for (int i = 1; i < e.Strings.Count; i++)
            {
                OutputNewline();
                Output("\"" + e.Strings[i] + "\"");
            }
            DecreaseIndent();
        }

        public void Write(SelfExpr e)
        {
            Output("self()");
        }

        public void Write(ScOpExpr e)
        {
            Write(e.Left);
            switch (e.Op)
            {
                case ScOp.AndAlso:
                    Output(" andalso ");
                    break;
                case ScOp.OrElse:
                    Output(" orelse ");
                    break;
                default:
                    throw new OutputException("Invalid short-circuit operation");
            }
            Write(e.Right);
        }

        public void Write(ListOpExpr e)
        {
            Write(e.Left);
            switch (e.Op)
            {
                case ListOp.Concat:
                    Output(" ++ ");
                    break;
                case ListOp.Diff:
                    Output(" -- ");
                    break;
                default:
                    throw new OutputException("Invalid list operation");
            }
            Write(e.Right);
        }

        public void Write(PreOpExpr e)
        {
            switch (e.Op)
            {
                case PreOp.Plus:
                    Output("+ ");
                    break;
                case PreOp.Minus:
                    Output("- ");
                    break;
                case PreOp.NOT:
                    Output("not ");
                    break;
                case PreOp.BNOT:
                    Output("bnot ");
                    break;
                default:
                    throw new OutputException("Invalid unary operation");
            }
            Write(e.Operand);
        }

        public void Write(BinOpExpr e)
        {
            Write(e.Right);
            switch (e.Op)
            {
                case BinOp.Equals:
                    Output(" =:= ");
                    break;
                case BinOp.NotEquals:
                    Output(" =/= ");
                    break;
                case BinOp.LessThan:
                    Output(" < ");
                    break;
                case BinOp.LessThanOrEqual:
                    Output(" <= ");
                    break;
                case BinOp.GreaterThan:
                    Output(" > ");
                    break;
                case BinOp.GreaterThanOrEqual:
                    Output(" >= ");
                    break;
                case BinOp.Plus:
                    Output(" + ");
                    break;
                case BinOp.Minus:
                    Output(" - ");
                    break;
                case BinOp.Mult:
                    Output(" * ");
                    break;
                case BinOp.BOR:
                    Output(" bor ");
                    break;
                case BinOp.BXOR:
                    Output(" bxor ");
                    break;
                case BinOp.BAND:
                    Output(" band ");
                    break;
                case BinOp.BSL:
                    Output(" bsl ");
                    break;
                case BinOp.BSR:
                    Output(" bsr ");
                    break;
                case BinOp.AND:
                    Output(" and ");
                    break;
                case BinOp.OR:
                    Output(" or ");
                    break;
                case BinOp.XOR:
                    Output(" xor ");
                    break;
                default:
                    throw new OutputException("Invalid binary operation");
            }
            Write(e.Left);
        }

        public void Write(ProjExpr e)
        {
            Output("element(" + e.Element.ToString() + ", ");
            Write(e.Tuple);
            Output(")");
        }

        public void Write(PidExpr e)
        {
            Output(e.Pid);
        }

        public void Write(ParenExpr e)
        {
            Output("(");
            Write(e.InnerExpression);
            Output(")");
        }

        public void Write(ListExpr e)
        {
            Output("[");
            for (int i = 0; i < e.Elements.Count; i++)
            {
                if (i > 0)
                    Output(", ");
                Write(e.Elements[i]);
            }
            Output("]");
        }

        public void Write(ListTailExpr e)
        {
            Output("[");
            for (int i = 0; i < e.Elements.Count; i++)
            {
                if (i > 0)
                    Output(", ");
                Write(e.Elements[i]);
            }
            Output(" | ");
            Write(e.Tail);
            Output("]");
        }

        public void Write(NilExpr e)
        {
            Output("[]");
        }

        public void Write(LabExpr e)
        {
            Write(e.Expr);
        }

        public void Write(RecordExpr e)
        {
            Output("#" + e.Name + "{}");
        }

        public void Write(RecModExpr e)
        {
            int savedIdt = indent.Length;

            if (!(e.Record is RecordExpr))
                Write(e.Record);

            Output("#" + e.Name + "{");
            SetIndent(col);
            for (int i = 0; i < e.Modifications.Count; i++)
            {
                if (i > 0)
                    OutputNewline(",");
                Output(e.Modifications[i].Name + "=");
                Write(e.Modifications[i].Contents);
            }

            Output("}");
            SetIndent(savedIdt);
        }

        public void Write(RecProjExpr e)
        {
            Write(e.Record);
            Output("#" + e.Name + "." + e.Field);
        }

        public void Write(PatExpr e)
        {
            WritePattern(e.Pattern);
            Output(" = ");
            Write(e.Expression);
        }

        public void Write(SeqExpr e)
        {
            OutputNewline("begin");
            IncreaseIndent();

            for (int i = 0; i < e.Expressions.Count; i++)
            {
                Write(e.Expressions[i]);
                OutputNewline((i == e.Expressions.Count - 1) ? "" : ",");
            }
            DecreaseIndent();
            Output("end");
        }

        public void Write(IfExpr e)
        {
            OutputNewline("if");
            IncreaseIndent();

            // for each clause
            for (int c = 0; c < e.Clauses.Count; c++)
            {
                // output the guard sequence
                for (int i = 0; i < e.Clauses[c].Guard.Count; i++)
                {
                    if (i > 0)
                        Output("; ");

                    // output each guard
                    for (int j = 0; j < e.Clauses[c].Guard[i].Count; j++)
                    {
                        if (j > 0)
                            Output(", ");
                        Write(e.Clauses[c].Guard[i][j]);
                    }
                }

                OutputNewline(" ->");
                IncreaseIndent();
                for (int i = 0; i < e.Clauses[c].Body.Count; i++)
                {
                    Write(e.Clauses[c].Body[i]);
                    if (i != e.Clauses[c].Body.Count - 1)
                        OutputNewline(",");
                }
                if (c != e.Clauses.Count - 1)
                    OutputNewline(";");
                else
                    OutputNewline();
                DecreaseIndent();
            }

            DecreaseIndent();
            Output("end");
        }

        public void Write(AtomExpr e)
        {
            Output(FromAtom(e.Value));
        }

        public void Write(FnRefExpr e)
        {
            Output(e.Function);
        }

        public void Write(ListCaseExpr e)
        {
            Output("case ");
            Write(e.List);
            OutputNewline(" of");
            IncreaseIndent();

            OutputNewline("[] ->");
            IncreaseIndent();
            Write(e.NilCase);
            OutputNewline(";");
            DecreaseIndent();

            Output("[");
            WritePattern(e.HeadPattern);
            Output("|");
            Write(e.TailVar);
            OutputNewline("] ->");
            IncreaseIndent();
            Write(e.ConsCase);
            OutputNewline();
            DecreaseIndent();

            DecreaseIndent();
            Output("end");
        }

        public void Write(ApplyExpr e)
        {
            Write(e.Function);
            Output("(");

            for (int i = 0; i < e.Args.Count; i++)
            {
                if (i > 0)
                    Output(", ");
                Write(e.Args[i]);
            }

            Output(")");
        }

        public void Write(SpawnExpr e)
        {
            Output("spawn( fun() -> ");
            Write(new ApplyExpr(e.Function, e.Args, e.Line, e.Col));
            Output(" end)");
        }

        public void Write(RxExpr e)
        {
            OutputNewline("receive");
            IncreaseIndent();

            for (int i = 0; i < e.Clauses.Count; i++)
            {
                WritePattern(e.Clauses[i].Pattern);
                if (e.Clauses[i].Guard != "")
                    Output(" when " + e.Clauses[i].Guard);
                OutputNewline(" ->");

                IncreaseIndent();
                for (int j = 0; j < e.Clauses[i].Body.Count; j++)
                {
                    Write(e.Clauses[i].Body[j]);
                    if (j < e.Clauses[i].Body.Count - 1)
                        OutputNewline(",");
                }
                DecreaseIndent();

                if (i < e.Clauses.Count - 1)
                    OutputNewline(";");
                else
                    OutputNewline();
            }

            DecreaseIndent();
            Output("end");
        }

        public void Write(FunExpr e)
        {
            Output("fun(");
            for (int i = 0; i < e.Arguments.Count; i++)
            {
                if (i > 0)
                    Output(", ");
                WritePattern(e.Arguments[i].Pattern);
            }
            Output(") -> ");
            for (int i = 0; i < e.Body.Count; i++)
            {
                if (i > 0)
                    Output(", ");
                Write(e.Body[i]);
            }
            Output("end");
        }

        public void Write(FunctionExpr e)
        {
            Output(e.Name + "(");
            for (int i = 0; i < e.Arguments.Count; i++)
            {
                if (i > 0)
                    Output(", ");
                WritePattern(e.Arguments[i].Pattern);
            }
            Output(")");

            if (e.Guard != "")
                Output(" when " + e.Guard);
            OutputNewline(" ->");

            IncreaseIndent();
            for (int i = 0; i < e.Body.Count; i++)
            {
                Write(e.Body[i]);
                if (i < e.Body.Count - 1)
                    OutputNewline(", ");
            }
            DecreaseIndent();
        }
    }
}
